package download

import (
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
)

const (
	userAgent                  = `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36`
	downloadResumePerRangeSize = 1024 * 2000
)

// Resume 断点续传
// 每次下载固定的小文件，以追加的方式写入目标文件。
func Resume(filePath string, url string) error {
	resumePoint := filePath + ".resumepoint"
	//targetFile, err := os.CreateOrUpdate(filePath)
	targetFile, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm)
	if err != nil {
		panic(err)
	}
	defer targetFile.Close()

	tempFile, _ := os.OpenFile(resumePoint, os.O_CREATE|os.O_RDWR, os.ModePerm)
	//1.读取临时文件中的数据，根据seek
	tempFile.Seek(0, io.SeekStart)
	// bs := make([]byte, 100, 100)
	bs := make([]byte, 100)
	tempFileBytesNum, _ := tempFile.Read(bs)
	// resumepoint 内容，eg: 当前数/总数/百分比
	countStr := string(bs[:tempFileBytesNum])
	countArr := strings.Split(countStr, "/")

	// 当前已下载字节长度
	var count int64 = 0
	// 总字节长度
	var total int64 = 0
	var percent = "0%"
	if len(countArr) >= 3 {
		count, _ = strconv.ParseInt(countArr[0], 10, 64)
		total, _ = strconv.ParseInt(countArr[1], 10, 64)
		percent = countArr[2]
		// 判断文件是否已下载完成
		if count >= total {
			fmt.Printf("[%s] 已下载完成，跳过本次任务\n", filePath)
			return nil
		}
	}
	fmt.Printf("[%s] 开始下载，总大小 %d, 已下载 %d, 进度 %s\n", filePath, total, count, percent)

	for {
		req, err := http.NewRequest(http.MethodGet, url, nil)
		if err != nil {
			return err
		}

		client := &http.Client{
			Transport: &http.Transport{
				TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
			},
		}

		// Range实现: fmt.Sprintf("bytes=%d-%d", h.StartPos, h.EndPos)
		downloadRange := fmt.Sprintf("bytes=%d-%d", count, count+(downloadResumePerRangeSize-1))
		req.Header.Set("Range", downloadRange)
		req.Header.Set("User-Agent", userAgent)
		resp, err := client.Do(req)
		if err != nil {
			fmt.Println("request fail:", err)
			return err
		}
		defer resp.Body.Close()

		//fmt.Println("Accept-Ranges:", resp.Header.Get("Accept-Ranges"))
		respContentRange := resp.Header.Get("Content-Range")

		totalRange := strings.Split(respContentRange, "/")
		if len(totalRange) >= 2 {
			total, _ = strconv.ParseInt(totalRange[1], 10, 64)
		}

		if resp.Status == "404 Not Found" {
			fmt.Printf("无法找到文件[%s] [%s]\n", url, resp.Status)
			return errors.New("404 Not Found")
		}

		// 断点续传下载正常响应码
		if resp.Status != "206 Partial Shell" {
			// 断点续传下载，偏移量值超文件最大值长度错误响应码
			if resp.Status == "416 Requested Range Not Satisfiable" {
				fmt.Printf("文件下载完毕 [%s]\n", filePath)
				tempFile.Close()
				Del(resumePoint)
			} else {
				fmt.Printf("文件传输异常报错：err[%s]\n", "resp.Status")
				continue
			}
			break
		}

		// 读取本次全部返回数据
		data, _ := io.ReadAll(resp.Body)

		// !!! 当前使用 os.O_APPEND 追加模式，不可设置 seek
		//targetFile.Seek(count, 0)

		// 本次写入的字节数
		writeByteCount, _ := targetFile.Write(data)
		count += int64(writeByteCount)
		percent = fmt.Sprintf("%.2f", float64(count)/float64(total)*100) + "%"
		fmt.Printf("[%s] 总大小 %d，本次下载 %d，总已下载 %d，下载进度 %s\n",
			filePath, total, writeByteCount, count, percent)

		// 记录断点，设置tempFile的下次读写起点为源点：0点，即覆盖重写
		//w := strconv.Itoa(int(count)) + "/" + strconv.Itoa(int(total)) + "/" + percent
		tempFile.Seek(0, io.SeekStart)
		tempFile.WriteString(fmt.Sprintf("%d/%d/%s", count, total, percent))
	}

	fileMD5, err := FileMD5(filePath)
	if err != nil {
		return err
	}
	fmt.Printf("文件[%s] md5: [%s]\n", filePath, fileMD5)

	return nil
}

////////////////////////

func TodoCopyFileResume() {
	/*
	   断点续传：
	       文件传递：文件复制
	       srcFile 复制到 destFile
	   思路：
	       边复制，边记录复制的总量（记录总复制字节数保存至tempFile）
	       复制一半中断后，若再次复制，会先读取tempFile文件里记录的已完成字节总数
	       然后将这个字节数作为文件读写offset（偏移量），
	       来结合whence（偏移位置）来决定后面读写文件的起点位置。
	       seek(offset,whence),设置指针光标的位置
	       第一个参数：偏移量
	       第二个参数：如何设置
	           0：seekStart表示相对于文件开始，
	           1：seekCurrent表示相对于当前偏移量，
	           2：seek end表示相对于结束。
	*/

	srcFile := "./0731/ddxc/pic_src.jpg"
	destFile := "./0731/ddxc/pic_des.jpg"
	tempFile := destFile + "temp.txt"
	file1, err := os.Open(srcFile)
	if err != nil {
		log.Fatalln(err)
	}
	file2, _ := os.OpenFile(destFile, os.O_CREATE|os.O_WRONLY, os.ModePerm)
	file3, _ := os.OpenFile(tempFile, os.O_CREATE|os.O_RDWR, os.ModePerm)

	defer file1.Close()
	defer file2.Close()
	//1.读取临时文件中的数据，根据seek
	file3.Seek(0, io.SeekStart)
	// bs := make([]byte, 100, 100)
	bs := make([]byte, 100)
	n1, _ := file3.Read(bs)
	fmt.Println(n1)
	countStr := string(bs[:n1])
	fmt.Println(countStr)
	count, _ := strconv.ParseInt(countStr, 10, 64)
	fmt.Println(count)

	//2. 设置读，写的偏移量
	file1.Seek(count, 0) // 设置file1下一次读或者写的起点
	file2.Seek(count, 0) // 设置file2下一次读或者写的起点
	// data := make([]byte, 1024, 1024)
	data := make([]byte, 1024)
	n2 := -1            // 读取的数据量， 默认值
	n3 := -1            //写出的数据量
	total := int(count) //读取的总量

	for {
		//3.读取数据
		// 基于上面的起点，读取file1文件len（data）个字节，
		// n2：实际读取的字节数（小于等于len（data），将读取的字节存入data。
		n2, err = file1.Read(data)
		if err == io.EOF {
			fmt.Println("文件复制完毕。。")
			file3.Close()
			//os.Remove(tempFile) //复制完，先不删除，验证最后存储的字节总数是不是跟复制的文件大小一致。
			break
		}
		//将数据写入到目标文件
		// 基于上面的起点，向file2文件写入len（data[:n2]）个字节，也就是写入data中前n2个元素；
		// n3：实际写入的字节数（小于等于n2）。
		n3, _ = file2.Write(data[:n2])
		total += n3
		//将复制总量，存储到临时文件中
		file3.Seek(0, io.SeekStart) // 设置file3的下次读写起点为源点：0点，即覆盖重写。
		file3.WriteString(strconv.Itoa(total))

		//假装断电
		//if total>1800000{
		//  panic("假装断电了。。。，假装的。。。")
		//}
	}
}
